/*
————————————————————————————————————————————————————————————————————————————————————
* 本文件用于实现程序运行部分
* 设计思路：以父进程实现控制运行模块，子进程实现运行的理念
* 运行后分为：
* 1. 运行结束、结果对
* 2. 运行结束、结果不对
* 3. 运行异常
* 4. 超时
* 5. 占用内存过大
* 需要通过这几种情况来设计出对应的输出，【但我们本文件只是用于运行，不对信息做处理】！！！！！
————————————————————————————————————————————————————————————————————————————————————
*/

#pragma once

#include <iostream>
#include <string>

#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "../Comm/Comm.hpp"
#include "../Comm/Log.hpp"

namespace ns_run
{
    class Runner
    {
    public:
        Runner() {}
        ~Runner() {}
    private:

        
    public:
        static int Run(const std::string& file, rlim_t cpu, rlim_t as)
        {
// 1. 先打开[运行结束->结果放置]文件

            std::string _stdout = FileOper::File::StdOut(file);
            std::string _stdin = FileOper::File::StdIn(file);
            std::string _stderr = FileOper::File::StdErr(file);
            std::string _exe = FileOper::File::Exe(file);

            umask(0);

            // 打开stdout文件，用于存放正常运行结束后输出结果的文件
            int out_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0644);
            if(out_fd < 0)
            {
                _log.LogMessage(ClassFile, Error, "运行期间open stdout失败");
                return System_Call_Err;
            }

            // 打开stderr文件，用于存放异常终止信息的文件
            int err_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0644);
            if(err_fd < 0)
            {
                _log.LogMessage(ClassFile, Error, "运行期间open stderr失败");
                return System_Call_Err;
            }

            // 存放一些测试用例
            int in_fd = open(_stdin.c_str(), O_CREAT | O_RDONLY, 0644);
            if(in_fd < 0)
            {
                _log.LogMessage(ClassFile, Error, "运行期间open stdin失败");
                return System_Call_Err;
            }

// 2. 创建子进程，让父进程管理运行模块，子进程负责运行

            pid_t pid = fork();
     // 创建子进程失败
            if (pid < 0)
            {
                close(out_fd);
                close(err_fd);
                close(in_fd);
                _log.LogMessage(ClassFile, Error, "运行期间fork失败");
                return System_Call_Err;
            }
    // 子进程
            else if (pid == 0)
            {

        // (1)对资源作出限制

                if(!SetRlimit(cpu, as)) return System_Call_Err;

        // (2)对当前的stdin、stdout、stderr作重定向

                // a. stdout -> file.stdout
                int ret_dup_out = dup2(out_fd, stdout->_fileno);
                if(ret_dup_out < 0)
                {
                    _log.LogMessage(ClassFile, Error, "运行期间dup2 stdout失败");
                    return System_Call_Err;
                }

                // b. stderr -> file.stderr
                int ret_dup_err = dup2(err_fd, stderr->_fileno);
                if(ret_dup_err < 0)
                {
                    _log.LogMessage(ClassFile, Error, "运行期间dup2 stderr失败");
                    return System_Call_Err;
                }

                // c. stdin -> file.stdin
                int ret_dup_in = dup2(in_fd, stdin->_fileno);
                if(ret_dup_in < 0)
                {
                    _log.LogMessage(ClassFile, Error, "运行期间dup2 stdin失败");
                    return System_Call_Err;
                }

        // (3) 子进程进行程序替换

                int ret_exec = execl(_exe.c_str(),_exe.c_str(), nullptr);

                _log.LogMessage(ClassFile, Error, "运行期间子进程程序替换失败");
                exit(System_Call_Err);
            }

// 3. 父进程等待子进程，并获取子进程的退出信号，并返回给上层
    /*
     *    在这里这个退出信号很重要，因为有的异常是不会重定向到stderr中，
     *    如达到CPU资源限制，只有OS向stderr写入，我们看不到，所以退出信号会告诉我们进程的异常信息
     */ 

            close(out_fd);
            close(err_fd);
            close(in_fd);

            int status = 0;
            int ret_waitpid = waitpid(pid, &status, 0);
            if(ret_waitpid < 0)
            {
                _log.LogMessage(ClassFile, Error, "运行期间父进程回收子进程失败");
                return System_Call_Err;
            }

            _log.LogMessage(ClassFile, Info, "运行结束，退出信号为 : %d\n", status & 0x7f);
            return status & 0x7f; // 返回的是子进程异常退出的退出信号
        }

    private:

        //设置资源上限
        static bool SetRlimit(int cpu, int as)
        {
            // 1. 设置运行时长上限，超过了被24号信号杀死

            rlimit r_cpu;
            r_cpu.rlim_cur = cpu;
            r_cpu.rlim_max = RLIM_INFINITY;
            int ret_set_cpu = setrlimit(RLIMIT_CPU, &r_cpu);
            if(ret_set_cpu < 0)
            {
                _log.LogMessage(ClassFile, Error, "运行期间设置CPU资源失败");
                return false;
            }

            /* 2. 设置可占用内存上限，超过内存上限被6号信号杀死
            * 在设置内存上限可能存在段错误，链接错误，以及为什么重定向后，有的信息可以显示，有的信息不可以显示，也在下面博客更新了，果然学无止境！！
            * https://blog.csdn.net/2302_76941579/article/details/139328125?spm=1001.2014.3001.5501
            */

            rlimit r_as;
            r_as.rlim_cur = as * 1024 * 512; //这里要记住内存分配的单位是byte，但是我这里设置基本单位是0.5MB，
                                             //【设置的大小】一定要保证可以让替换后的程序的库文件和正文代码数据都加载成功
            r_as.rlim_max = RLIM_INFINITY;
            int ret_set_as = setrlimit(RLIMIT_AS, &r_as);
            if(ret_set_as < 0)
            {
                _log.LogMessage(ClassFile, Error, "运行期间设置进程地址空间资源失败");
                return false;
            }

            return true;
        }

    };
};